﻿namespace BM.Core
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.IO;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using BM.Common;
    using Microsoft.Win32;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    public class Agent
    {
        private bool IsBSSuperChanger;

        /// <summary>
        /// Contain bluestack manage config etc: datapath, repository path, backup list, auto run app, ...
        /// </summary>
        public BMConfig _bmConfig { get; set; }
        /// <summary>
        /// Bluestack config: something like width, height, memory ram...
        /// </summary>
        public BSConfig _bsConfig { get; set; }

        /// <summary>
        /// Initialize new instance of BM Agent class
        /// </summary>
        public Agent(bool isBSSuperChanger)
        {
            this.IsBSSuperChanger = isBSSuperChanger;
            try {
                Log.Write("Nạp thông tin cấu hình Bluestack Manager");
                _bmConfig = BMConfig.LoadSetting();
                Log.Write("Nạp thông tin BlueStack");
                _bsConfig = BSConfig.LoadSetting();
            }
            catch (Exception ex) {
                Log.Write(ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Create backup with specified path
        /// </summary>
        /// <param name="path">Folder will contain this backup</param>
        public void CreateBackup(string path)
        {
            try {
                Log.Write("Tạo backup " + path);

                Log.Write("1. Copy dữ liệu...");
                var taskList = new List<Task>();
                var dataPaths = _bmConfig.DataPaths.Split('|');
                foreach (var dataPath in dataPaths) {
                    var dataPathTemp = dataPath;
                    var t = new Task(() => this.Copy(dataPathTemp, path));
                    taskList.Add(t);
                    t.Start();
                }
                try {
                    Task.WaitAll(taskList.ToArray());
                }
                catch (AggregateException aex) {
                    foreach (Exception ex in aex.InnerExceptions) {
                        Log.WriteFormat("Xảy ra lỗi khi copy {0}", ex.Message);
                    }
                    throw;
                }

                Log.Write("2. Tạo bản ghi backup " + path);
                _bmConfig.BackUps.Add(path);

                // if BS Super Changer then uncomment this command to save GUI
                // this.SaveGUID(path);

                // Save setting
                BMConfig.SaveSetting(_bmConfig);
            }
            catch (Exception ex) {
                Log.Write(ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Delete Backup Path
        /// </summary>
        /// <param name="path">Backup path</param>
        public void DeleteBackup(string path)
        {
            try {
                // Delete physic data
                this.Delete(path);

                // Delete record in database
                Log.Write("Xóa bản ghi dữ liệu " + path);
                if (_bmConfig.BackUps.Contains(path))
                    _bmConfig.BackUps.Remove(path);

                // Then save database
                BMConfig.SaveSetting(_bmConfig);
            }
            catch (Exception ex) {
                Log.WriteFormat("Xảy ra lỗi khi xóa dữ liệu : {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Start target backup with specified path and target app
        /// </summary>
        /// <param name="path">Backup path</param>
        /// <param name="appName">App name. If app name is empty then BS will be run.</param>
        public void StartBackup(string path, string appName)
        {
            try {
                // Try to killBs before start
                this.KillBS();

                // if BS Super Changer then load GUID
                if (IsBSSuperChanger) this.LoadGuid(path);

                // Change symbolic link
                // 1 is Data 
                var block1 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\BlueStacks\Guests\Android\BlockDevice\1", true);
                block1.SetValue("Path", path + "Data.sparsefs");
                block1.Close();
                // 2 is SDCard
                var block2 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\BlueStacks\Guests\Android\BlockDevice\2", true);
                block1.SetValue("Path", path + "SDCard.sparsefs");
                block2.Close();

                // Run BS
                this.RunBS(appName);
            }
            catch (Exception ex) {
                Log.Write(ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Stop Backup
        /// </summary>
        /// <param name="path">backup path</param>
        public void StopBackup(string backUpPath)
        {
            try {
                // Try to kill BS
                KillBS();

                // Change SBL to origin
                // 1 is Data
                var block1 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\BlueStacks\Guests\Android\BlockDevice\1", true);
                block1.SetValue("Path", _bsConfig.DataDir + "Android\\Data.sparfse");
                block1.Close();

                // 2 is SDCard
                var block2 = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\BlueStacks\Guests\Android\BlockDevice\2", true);
                block1.SetValue("Path", _bsConfig.DataDir + "Android\\SDCard.sparfse");
                block2.Close();

                // if bs super changer then save guid
                if (IsBSSuperChanger) this.SaveGuid(backUpPath);
                // Sleep 300 for safe purpose
                Thread.Sleep(100);
            }
            catch (Exception ex) {
                Log.WriteFormat("Ngoại lệ khi đóng backup : {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Load Guid from text file to Registry
        /// </summary>
        /// <param name="path"></param>
        private void LoadGuid(string backupPath)
        {
            try {
                // Need to check
                Log.Write("Nạp GUID vào registry");
                var userGuid = File.ReadAllText(backupPath + "\\guid.txt");
                var bsKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\BlueStacks\\", true);
                bsKey.SetValue("USER_GUID", userGuid);
                bsKey.Close();
            }
            catch {
                throw;
            }
        }

        /// <summary>
        /// Save Guid from registry to text file
        /// </summary>
        /// <param name="path"></param>
        private void SaveGuid(string backUpPath)
        {
            try {
                Log.Write("Lưu registry vào file");
                var bsKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\BlueStacks\\", true);
                var userGuid = bsKey.GetValue("USER_GUID").ToString();
                bsKey.Close();
                File.WriteAllText(backUpPath + "guid.txt", userGuid);
            }
            catch {
                throw;
            }
        }

        /// <summary>
        /// Run Bluestack with specified app name
        /// </summary>
        /// <param name="appName">App you want to run. If app name is empty then Bluestack will open without app.</param>
        public void RunBS(string appName = "")
        {
            try {
                if (string.IsNullOrEmpty(appName.Trim())) {
                    Log.Write("Chạy bluestack");

                    Process.Start(_bsConfig.InstallDir + "HD-StartLauncher.exe");
                }
                else {
                    Log.Write("Chạy ứng dụng " + appName);

                    var appJsonPath = _bsConfig.DataDir + "UserData\\Gadget\\apps.json";

                    var appsJsonString = File.Exists(appJsonPath) ?
                        File.ReadAllText(appJsonPath) : File.ReadAllText(appJsonPath + ".bak");

                    var apps = JsonConvert.DeserializeObject<JArray>(appsJsonString);

                    foreach (var app in apps) {
                        if (!app["name"].ToString().ToLower().Equals(appName.ToLower())) continue;

                        var appArg = "-p " + app["package"] + " -a " + app["activity"];
                        Process.Start(_bsConfig.InstallDir + "HD-RunApp.exe ", appArg);

                        break;
                    }
                }

                Thread.Sleep(500);
            }
            catch (Exception ex) {
                Log.WriteFormat("RunBS exception {0}", ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Kill Bluestack
        /// </summary>
        public void KillBS()
        {
            Log.Write("Tắt process của Bluestack");

            // Loop through all processes and find if process contain HD- then kill
            foreach (var process in Process.GetProcesses()) {
                try {
                    if (process.ProcessName.Contains("HD-") && !process.HasExited) {
                        process.Kill();
                        Thread.Sleep(100);
                    }
                }
                catch (Exception ex) {
                    Log.Write("Kill process gặp lỗi : " + ex.Message);
                }
            }

            Thread.Sleep(500);
        }

        /// <summary>
        /// Scan backup in specified folder
        /// </summary>
        /// <param name="repository">Repository folder path</param>
        public void ScanBackUp(string repository)
        {
            Log.Write("Quét backup tại thư mục " + repository);

            var backUpPaths = Directory.GetDirectories(repository);
            // You can improve this code
            // Loop through per path and get child folder.
            // Check if it contain data path then must add this to repository
            foreach (var backUpPath in backUpPaths) {
                _bmConfig.BackUps.Add(backUpPath);
            }
            BMConfig.SaveSetting(_bmConfig);
        }

        /// <summary>
        /// Copy data from data path to destination path
        /// </summary>
        /// <param name="dataPath">data paths : path of data want to copy</param>
        /// <param name="bsDataDir">data dir : folder contain data paths </param>
        /// <param name="backUpDir">backup dir : destination directory</param>
        private void Copy(string dataPath, string backUpDir)
        {
            try {
                Log.Write("Copy dữ liệu từ " + dataPath + " đến " + backUpDir);

                if (File.Exists(dataPath)) {
                    // Travel through node != leaf and create directory then copy leaf
                    var dest = dataPath.Replace(_bsConfig.DataDir, backUpDir);
                    var nodeList = dest.Substring(dest.IndexOf(@"\") + 1, dest.LastIndexOf(@"\") - 1 - dest.IndexOf(@"\")).Split('\\');
                    var root = dest.Substring(0, dataPath.IndexOf('\\') + 1);
                    for (var i = 0; i < nodeList.Length; ++i) {
                        root += nodeList[i] + "\\";
                        if (!Directory.Exists(root)) {
                            Directory.CreateDirectory(root);
                            Log.Write("Tạo thư mục " + root);
                        }
                    }
                    File.Copy(dataPath, dest, true);
                    Log.Write("Copy file từ " + dataPath + " đến " + dest);
                }
                else if (Directory.Exists(dataPath)) {
                    var tree = GetTree(dataPath);
                    foreach (var node in tree) {
                        var replacePath = node.Replace(_bsConfig.DataDir + "Android", backUpDir);
                        if (Directory.Exists(node)) {
                            Log.Write("Tạo thư mục " + replacePath);
                            Directory.CreateDirectory(replacePath);
                        }
                        else if (File.Exists(node)) {
                            Log.Write("Copy file từ " + node + " đến " + replacePath);
                            File.Copy(node, replacePath, true);
                        }
                    }
                }
            }
            catch (Exception ex) {
                Debug.WriteLine(ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Delete file
        /// </summary>
        private void Delete(string path)
        {
            try {
                Log.Write("Xóa " + path);
                if (Directory.Exists(path)) Directory.Delete(path, true);
                if (File.Exists(path)) File.Delete(path);
            }
            catch (Exception ex) {
                Log.Write("Lỗi khi xóa : " + ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Get all file and folder
        /// </summary>
        /// <param name="directory">parent directory - root node</param>
        /// <returns>directory, file tree</returns>
        private List<string> GetTree(string directory)
        {
            Log.Write("Lấy thông tin thư mục " + directory);

            var entireTree = new List<string>();
            entireTree.AddRange(GetNodes(directory));
            return entireTree;
        }

        /// <summary>
        /// Get all node of true
        /// </summary>
        /// <param name="currentNode">Root node</param>
        /// <returns>Tree from current node</returns>
        private List<string> GetNodes(string currentNode)
        {
            Log.Write("Lấy thông tin thư mục " + currentNode);

            // add current directory to node list
            var nodes = new List<string> { currentNode };

            // recursive directory
            Directory
                .GetDirectories(currentNode)
                .ToList()
                .ForEach(subNode => nodes.AddRange(GetNodes(subNode)));

            // add all file
            Directory
                .GetFiles(currentNode)
                .ToList()
                .ForEach(leaf => nodes.Add(leaf));

            return nodes;
        }
    }
}
